package com.njzxw.fs.commonutil.util;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.swing.*;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 异步加载bean
 */
@Component
public class LoadSpringBean {

    @Value("${spring.application.name}")
    private String serverId;

    @Value("${server.port}")
    private String port;

    public org.slf4j.Logger logger = LoggerFactory.getLogger(LoadSpringBean.class);

    public List<Class<?>> loadClass(String filePath, ClassLoader beanClassLoader) {

        List<Class<?>> classList = new ArrayList<>();
        File file = new File(filePath);
        if (file.exists() && file.isDirectory()) {

            Stack<File> stack = new Stack<>();
            stack.push(file);
            while (!stack.isEmpty()) {
                File path = stack.pop();
                // 只需要class文件或者jar包
                File[] classFiles = path.listFiles(new FileFilter() {
                    public boolean accept(File pathname) {
                        return pathname.isDirectory() || pathname.getName().endsWith(".class")
                                || pathname.getName().endsWith(".jar");
                    }
                });

                for (File subFile : classFiles) {
                    if (subFile.isDirectory()) {
                        // 如果是目录，则加入栈中
                        stack.push(subFile);
                    } else {
                        URL url = null;
                        JarFile jar = null;
                        Method method = null;
                        Boolean accessible = null;
                        String className = subFile.getAbsolutePath();
                        try {
                            // 反射并调用URLClassLoader的addURL方法
                            URLClassLoader classLoader = (URLClassLoader) beanClassLoader;
                            method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
                            accessible = method.isAccessible();
                            if (accessible == false) {
                                method.setAccessible(true);
                            }

                            if (className.endsWith(".class")) {
                                // 一次性加载目录下的所有class文件 这里一定不要写成url = subFile.toURI().toURL();
                                url = file.toURI().toURL();
                                method.invoke(classLoader, url);
                                // 拼类名，并进行类型加载
                                int clazzPathLen = file.getAbsolutePath().length() + 1;
                                className = className.substring(clazzPathLen, className.length() - 6);
                                className = className.replace(File.separatorChar, '.');
                                classList.add(classLoader.loadClass(className));
                            } else if (className.endsWith(".jar")) {
                                // 如果是jar包，加载该jar包
                                url = subFile.toURI().toURL();
                                method.invoke(classLoader, url);
// 获取jar
                                jar = new JarFile(new File(className));
// 从此jar包 得到一个枚举类
                                Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
                                while (entries.hasMoreElements()) {
                                    // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                                    JarEntry entry = entries.nextElement();
                                    String name = entry.getName();
                                    // 如果是以/开头的
                                    if (name.charAt(0) == '/') {
// 获取后面的字符串
                                        name = name.substring(1);
                                    }
                                    name = name.replace(File.separatorChar, '.');
                                    // 获取class文件
                                    if (name.endsWith(".class") && !entry.isDirectory()) {
                                        String className1 = name.substring(0, name.length() - 6);
                                        // 添加到classes
                                        Class<?> classs = classLoader.loadClass(className1);
                                        classList.add(classs);
                                    }
                                }
                            }
                        } catch (Exception e) {
                            logger.error(e.getMessage());
                            throw new RuntimeException(e.getMessage());
                        } finally {
                            if (null != jar) {
                                try {
                                    jar.close();
                                } catch (IOException e) {
                                    logger.error(e.getMessage());
                                    throw new RuntimeException(e.getMessage());
                                }
                            }
                            if (null != method && null != accessible) {
                                method.setAccessible(accessible);
                            }
                        }

                    }
                }
            }
        }
        return classList;
    }



    /**
     * 卸载bean注册
     */
    public void unReload(){
        String lib = System.getProperty("user.dir") + File.separator + "lib"+File.separator + serverId+port;
        File file = new File(lib);
        if (!file.exists()) {
            file.mkdirs();
        }
        logger.debug("lib:" + lib);

        RequestMappingHandlerMapping requestMappingHandlerMapping = SpringContextUtils.getApplicationContext()
                .getBean(RequestMappingHandlerMapping.class);

        ApplicationContext applicationContext = SpringContextUtils.getApplicationContext();
        ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
        ClassLoader beanClassLoader = beanFactory.getBeanClassLoader();
        List<Class<?>> clazzs = loadClass(lib, beanClassLoader);
        logger.debug("clazzs:" + clazzs.size());
        for (Class<?> clazz : clazzs) {
            if (!clazz.isInterface()) {
                if (clazz.getAnnotation(Controller.class) != null ||
                        clazz.getAnnotation(RestController.class) != null ||
                        clazz.getAnnotation(Service.class) != null ||
                        clazz.getAnnotation(Component.class) != null) {
//                    classList.add(classs);
                    logger.debug("clazzs111:" + clazz.getSimpleName());
                    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
                    try {
                        beanFactory.removeBeanDefinition(StringUtils.uncapitalize(clazz.getSimpleName()));
                    } catch (Exception e) {
                    }
                    if (clazz.getAnnotation(Controller.class) != null || clazz.getAnnotation(RestController.class) !=
                            null) {
                        unReloadRegisterMapping(requestMappingHandlerMapping, clazz);
                    }
                }
            }
        }
    }

    public void unReloadRegisterMapping(RequestMappingHandlerMapping requestMappingHandlerMapping, Class<?> clazz) {
        try{
            Method[] targetMethods = clazz.getDeclaredMethods();

            //class前缀
            String url_ = "";
            RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
            if (requestMapping != null) {
                url_ = requestMapping.value()[0];
            }

            for (int i = 0; i < targetMethods.length; i++) {
                Method targetMethod = targetMethods[i];
                String method_url = "";
                requestMapping = targetMethod.getAnnotation(RequestMapping.class);
                if (requestMapping != null) {
                    method_url = requestMapping.value()[0];
                }
                logger.debug("读取JAR的URL:" + url_ + method_url);
                PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition(url_ + method_url);
                RequestMethodsRequestCondition requestMethodsRequestCondition = new RequestMethodsRequestCondition
                        (RequestMethod.POST, RequestMethod.GET);

                ParamsRequestCondition paramsRequestCondition = new ParamsRequestCondition();

                RequestMappingInfo mapping_info = new RequestMappingInfo(patternsRequestCondition,
                        requestMethodsRequestCondition, paramsRequestCondition, null, null, null, null);
                unregisterMapping(requestMappingHandlerMapping,url_ + method_url);
                try {
                    requestMappingHandlerMapping.unregisterMapping(mapping_info);
                } catch (Exception e) {
                }

            }
        }catch (Exception e){

        }

    }

    public void reload() {

        String lib = System.getProperty("user.dir") + File.separator + "lib"+File.separator + serverId+port;
        File file = new File(lib);
        if (!file.exists()) {
            file.mkdirs();
        }
        logger.debug("lib:" + lib);

        RequestMappingHandlerMapping requestMappingHandlerMapping = SpringContextUtils.getApplicationContext()
                .getBean(RequestMappingHandlerMapping.class);

        ApplicationContext applicationContext = SpringContextUtils.getApplicationContext();
        ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
        ClassLoader beanClassLoader = beanFactory.getBeanClassLoader();
        List<Class<?>> clazzs = loadClass(lib, beanClassLoader);
        logger.debug("clazzs:" + clazzs.size());
        for (Class<?> clazz : clazzs) {
            if (!clazz.isInterface()) {
                if (clazz.getAnnotation(Controller.class) != null ||
                        clazz.getAnnotation(RestController.class) != null ||
                        clazz.getAnnotation(Service.class) != null ||
                        clazz.getAnnotation(Component.class) != null) {
//                    classList.add(classs);
                    logger.debug("clazzs111:" + clazz.getSimpleName());
                    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
                    try {
                        beanFactory.removeBeanDefinition(StringUtils.uncapitalize(clazz.getSimpleName()));
                    } catch (Exception e) {
                    }
                    beanFactory.registerBeanDefinition(StringUtils.uncapitalize(clazz.getSimpleName()),
                            beanDefinitionBuilder.getRawBeanDefinition());
                    if (clazz.getAnnotation(Controller.class) != null || clazz.getAnnotation(RestController.class) !=
                            null) {
                        registerMapping(requestMappingHandlerMapping, clazz);
                    }
                }
            }
        }

    }


    public void registerMapping(RequestMappingHandlerMapping requestMappingHandlerMapping, Class<?> clazz) {


        Method[] targetMethods = clazz.getDeclaredMethods();

        //class前缀
        String url_ = "";
        RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
        if (requestMapping != null) {
            url_ = requestMapping.value()[0];
        }

        for (int i = 0; i < targetMethods.length; i++) {
            Method targetMethod = targetMethods[i];
            String method_url = "";
            requestMapping = targetMethod.getAnnotation(RequestMapping.class);
            if (requestMapping != null) {
                method_url = requestMapping.value()[0];
            }
            logger.debug("读取JAR的URL:" + url_ + method_url);
            PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition(url_ + method_url);
            RequestMethodsRequestCondition requestMethodsRequestCondition = new RequestMethodsRequestCondition
                    (RequestMethod.POST, RequestMethod.GET);

            ParamsRequestCondition paramsRequestCondition = new ParamsRequestCondition();

            RequestMappingInfo mapping_info = new RequestMappingInfo(patternsRequestCondition,
                    requestMethodsRequestCondition, paramsRequestCondition, null, null, null, null);
            unregisterMapping(requestMappingHandlerMapping,url_ + method_url);
            requestMappingHandlerMapping.registerMapping(mapping_info, StringUtils.uncapitalize(clazz.getSimpleName()
            ), targetMethod); // 注册映射处理

        }
    }

    public void unregisterMapping(RequestMappingHandlerMapping requestMappingHandlerMapping,String urlMapping) {
        try {
            RequestMappingHandlerMapping mapping = SpringContextUtils.getBean(RequestMappingHandlerMapping.class);
            // 获取url与类和方法的对应信息
            Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();

            List<Map<String, String>> list = new ArrayList<Map<String, String>>();
            for (Map.Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
                RequestMappingInfo info = m.getKey();
                HandlerMethod method = m.getValue();
                PatternsRequestCondition p = info.getPatternsCondition();
                for (String url : p.getPatterns()) {
                    if (urlMapping.equals(url) || urlMapping.equals(url.substring(1))) {
                        try {
                            requestMappingHandlerMapping.unregisterMapping(info);
                        } catch (Exception e) {
                        }
                    }
                }
            }
        }catch (Exception e){}
    }

}
